feat: Implement SEP-2260 require server requests to associate with client requests#1027
feat: Implement SEP-2260 require server requests to associate with client requests#1027alexhancock wants to merge 1 commit into
Conversation
ccdbd48 to
070220b
Compare
070220b to
d772e4c
Compare
| } | ||
| let strict = | ||
| peer_info.is_some_and(|info| info.protocol_version >= ProtocolVersion::V_2026_07_28); | ||
| if strict && !in_request_handler_scope { |
There was a problem hiding this comment.
Handshake-free services can enter serve_inner with peer_info == None and take the protocol version from each request's metadata
rust-sdk/crates/rmcp/src/service/server.rs
Lines 548 to 564 in d772e4c
Here that makes strict false even after the stateless lifecycle has been selected. How should an unassociated send be rejected on that supported path when no handshake-derived PeerInfo exists?
| let originating_request = crate::service::ORIGINATING_REQUEST | ||
| .try_with(|id| id.clone()) | ||
| .ok(); | ||
| let handle = tokio::spawn(async move { | ||
| let result = future.await; | ||
| let result = run_task_operation(originating_request, future).await; |
There was a problem hiding this comment.
TaskManager::spawn returns the task to the handler immediately, so this child future can con
tinue after the originating JSON-RPC response has closed. Re-establishing the task-local makes
a later direct sampling or elicitation pass the guard anyway.
| let handler_id = id.clone(); | ||
| spawn_service_task(async move { | ||
| let result = service | ||
| .handle_request(request, context) | ||
| let result = ORIGINATING_REQUEST | ||
| .scope(handler_id, service.handle_request(request, context)) |
There was a problem hiding this comment.
Since Tokio task-local values are not inherited by tokio::spawn, a handler that spawns sampling work and awaits its JoinHandle before returning is rejected even though the parent client request is still in flight?
|
These changes are now included in #1029 with more of what we need! Closing this one |
…equests (SEP-2260) Reuses the ORIGINATING_REQUEST task-local from modelcontextprotocol#1027; the marker rides the request's non-serialized Extensions so the streamable HTTP server can route associated requests to the originating SSE stream.
Implements SEP-2260.
Tracking issue: #873
What
Servers MUST send
sampling/createMessage,elicitation/create, androots/listrequests only in association with an originating client request (e.g. duringtools/call,resources/read, orprompts/getprocessing). Standalone server-initiated requests of these types are not supported.pingis excepted.How
tokio::task_local!(ORIGINATING_REQUEST) that is scoped around each inbound client-request handler in the serve loop, so any server→client request issued while handling a client request is recognized as associated.Peer<RoleServer>::ensure_request_association, called from the server-side outbound request helpers (create_message,create_elicitation,create_elicitation_with_timeout,list_roots). When the negotiated protocol version is2026-07-28or newer and the call is made outside a request-handler scope, it returns an error instead of sending an unassociated request.pinguses a separate path and is not affected.ProtocolVersion::V_2026_07_28so older peers keep legacy behavior.Notes
Test
Added
test_sep_2260_request_associationverifying nested sampling insidecall_toolsucceeds while a standalone (spawned) sampling request outside the handler scope is rejected under2026-07-28.